[JavaScript] Method Chaining

前言

最近在寫 canvas-factory ,主要是一個把其他函式庫的功能整合到 Canvas 上的一個套件,像是製作 GIFMP4 等等。其中想要把 API 寫成 Chaining 的形式,但其中又有許多非同步的操作,在此紀錄一下實現結果。

Method Chaining

若想要達到 Method Chaining 的效果,只要在函式中回傳自己即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var foo = function(n){
var number = n;
this.add = function(n){
number += n;
return this;
}
this.sub = function(n){
number -= n;
return this;
}
this.toValue = function(){
return number;
}
return this;
}
console.log(foo(1).add(1).add(1).sub(1).add(1).toValue());

Async Method Chaining

若想要把非同步的事件也串起來,我們可以使用 Promise 的概念,把函式存起來且記錄狀態。

使用 handlefulfillresolve 分別來處理欲執行的函式。

首先將所有 public method 都利用 handle 包一層,執行完該程式則呼叫 fulfill

1
2
3
4
5
6
7
8
this.addAsync = function(n){
return handle(function(){
setTimeout(function(){
number += n;
fulfill();
},500);
})
}

handle 中只把該函式存起來而不執行。

1
2
3
4
function handle(fn){
queue.push(fn);
return resolve();
}

resolve 中判斷當前狀態去執行函式,一樣回傳自己來繼續串下去。

1
2
3
4
5
6
7
var resolve = function(){
if ( queue.length !== 0 && !isPending ){
isPending = true;
queue[0]();
}
return this;
}.bind(this);

fulfill 中代表當前執行的函式執行完畢,呼叫 resolve 繼續執行下一個。

1
2
3
4
5
function fulfill(){
isPending = false;
queue.shift();
resolve();
}

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
var Foo = function(n){
var isPending = false;
var queue = [];
var number = n;
this.add = function(n){
return handle(function(){
number += n;
fulfill();
});
}
this.sub = function(n){
return handle(function(){
number -= n;
fulfill();
});
}
this.addAsync = function(n){
return handle(function(){
setTimeout(function(){
number += n;
fulfill();
},500);
})
}
this.getValue = function(fn){
return handle(function(){
fn(number);
fulfill();
})
}
function handle(fn){
queue.push(fn);
return resolve();
}
function fulfill(){
isPending = false;
queue.shift();
resolve();
}
var resolve = function(){
if ( queue.length !== 0 && !isPending ){
isPending = true;
queue[0]();
}
return this;
}.bind(this);
}
var foo = new Foo(1);
foo
.add(1)
.addAsync(1)
.sub(1)
.addAsync(1)
.getValue(function(value){
console.log(value);
})
.add(1)
.addAsync(1)
.getValue(function(value){
console.log(value);
})
foo
.add(1)
.addAsync(1)
.getValue(function(value){
console.log(value);
})